/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package arbol_sintactico_abstracto;

import analizadorlexico.Token;
import java.io.IOException;
import java.util.List;
import tabla_de_simbolos.Clase;
import tabla_de_simbolos.Constructor;
import tabla_de_simbolos.ErrorSemantico;
import tabla_de_simbolos.Metodo;
import tabla_de_simbolos.ModificadorMetodo;
import tabla_de_simbolos.TablaDeSimbolos;
import tabla_de_simbolos.Tipo;
import generador_de_codigo.GeneradorDeCodigo;

/**
 *
 * @author Javi
 */
public class Identificador extends Primario {
    private Token id;
    private List<Expresion> args;
    private List<Llamada> listaLlamadas;

    public Identificador(Token id, List<Expresion> args, List<Llamada> listaLlamadas) {
        this.id = id;
        this.args = args;
        this.listaLlamadas = listaLlamadas;
    }
  
    
 
    public Token getId() {
        return id;
    }

    public void setId(Token id) {
        this.id = id;
    
    }
    
    
    @Override
    public Tipo check(Clase clase, Constructor metodo, TablaDeSimbolos ts) throws ErrorSemantico, IOException { 
        if(metodo.getModMetodo().getModificador().equals("static")) //EL METODO ES ESTATICO!
            return metodoStatico(clase,metodo,ts);        
        else if(metodo.getModMetodo().getModificador().equals("dynamic")) // EL METODO ES DINAMICO! 
           return metodoDinamico(clase,metodo,ts);
        return null;
    }              
   

    private Tipo checkStatic(Constructor metodo) throws IOException {
        int i = 0;  
        Tipo t = null;
        while(t == null && i < metodo.getParametros().size()){
            if(metodo.getParametros().get(i).getNombre().equals(id.getLexema())){
                t = metodo.getParametros().get(i).getTipo();
                GeneradorDeCodigo.generar("LOAD " + (2 + metodo.getOffsetParam() - metodo.getParametros().get(i).getOffset()) + "\n");
            }
            i++;
        }
        i = 0;  
        while(t == null && i < metodo.getVariables().size()){
            if(metodo.getVariables().get(i).getNombre().equals(id.getLexema())){
                t = metodo.getVariables().get(i).getTipo();
                GeneradorDeCodigo.generar("LOAD " + metodo.getVariables().get(i).getOffset() + "\n");
            }
            i++;
        }
        return t;
    }

    private Tipo checkDynamic(Clase clase, Constructor metodo) throws IOException {
        int i = 0; 
        Tipo t = null;
        while(t == null && i < metodo.getParametros().size()){
            if(metodo.getParametros().get(i).getNombre().equals(id.getLexema())){
                t = metodo.getParametros().get(i).getTipo();
                GeneradorDeCodigo.generar("LOAD " + (3 + metodo.getOffsetParam() - metodo.getParametros().get(i).getOffset()) + "\n");
            }
            i++;
        }
        i = 0;  
        while(t == null && i < metodo.getVariables().size()){
            if(metodo.getVariables().get(i).getNombre().equals(id.getLexema())){
                t = metodo.getVariables().get(i).getTipo();
                GeneradorDeCodigo.generar("LOAD " + metodo.getVariables().get(i).getOffset() + "\n");
            }
            i++;
        }
        i = 0;  
        while(t == null && i < clase.getListaAtributos().size()){
            if(clase.getListaAtributos().get(i).getNombre().equals(id.getLexema())){
                t = clase.getListaAtributos().get(i).getTipo();
                GeneradorDeCodigo.generar("LOAD 3 \n");
                GeneradorDeCodigo.generar("LOADREF " + clase.getListaAtributos().get(i).getOffset() + "\n");
            }
            i++;
        }
        return t;
    }    

    private Tipo metodoStatico(Clase clase, Constructor metodo, TablaDeSimbolos ts) throws IOException, ErrorSemantico {
        Tipo t = null; 
        ModificadorMetodo mod = null;
        
        if(args == null && listaLlamadas == null){ //ES UN IDENTIFICADOR
            t = checkStatic(metodo);                
            if(t == null)
                throw new ErrorSemantico("la variable '" + id.getLexema() + "' no es accesible para metodo estatico. Linea: " + id.getLinea());
        }                
        else if(args == null) // ES UN IDENTIFICADOR SEGUIDO DE UNA LLAMADA
            t = identifcador_llamada_estatico(clase, metodo, t, mod, ts); 

        else if(args != null)  //ES UN METODO SEGUIDO DE UNA LLAMDA
            t = metodo_llamada_estatico(clase, metodo, t, mod, ts);
                        
        return t;
    }

    private Tipo metodoDinamico(Clase clase, Constructor metodo, TablaDeSimbolos ts) throws IOException, ErrorSemantico {
        Tipo t = null; 
        ModificadorMetodo mod = null;   

        if(args == null && listaLlamadas == null){  // ES UN IDENTIFICADOR
            t = checkDynamic(clase, metodo);
            if(t == null)
                throw new ErrorSemantico("la variable '"+ id.getLexema() +"' no se encuentra definida. Linea: " + id.getLinea());
        }                       
        else if(args == null){ // ES UN IDENTIFICADOR SEGUIDO DE UNA LLAMADA
            t = identifcador_llamada_dinamico(clase, metodo,t,mod,ts);            
        }
        else if(args != null) //ES UN METODO SEGUIDO DE UNA LLAMDA
            t = metodo_llamada_dinamico(clase, metodo,t,mod,ts);           
        
        return t;
    }

    private Tipo identifcador_llamada_dinamico(Clase clase, Constructor metodo, Tipo t, ModificadorMetodo mod, TablaDeSimbolos ts) throws ErrorSemantico, IOException {
        if(ts.getListaClases().containsKey(id.getLexema())){//x es una clase
            t = ts.getListaClases().get(id.getLexema());
            mod = new ModificadorMetodo(new Token("static", "static", -1));
            for(Llamada llamada : listaLlamadas){
                 t = llamada.check(clase, metodo,t,mod,ts);
                 mod = new ModificadorMetodo(new Token("dynamic", "dynamic", -1));
             }                     
        }
        else{
            t = checkDynamic(clase, metodo);
            if(t == null)
                throw new ErrorSemantico("la variable '" + id.getLexema() + "' no se encuentra definida. Linea: " + id.getLinea());
            mod = new ModificadorMetodo(new Token("dynamic", "dynamic", -1));
            for(Llamada llamada : listaLlamadas){
                 t = llamada.check(clase, metodo,t,mod,ts);
            } 
        }                
        if(t == null)
            throw new ErrorSemantico("la variable '" + id.getLexema() + "' no es accesible para metodo estatico. Linea: " + id.getLinea());

        return t;
    }

     private Tipo identifcador_llamada_estatico(Clase clase, Constructor metodo, Tipo t, ModificadorMetodo mod, TablaDeSimbolos ts) throws ErrorSemantico, IOException {
            if(ts.getListaClases().containsKey(id.getLexema())){//es metodo estatico NOMBRE_CLASE.m1()...
                t = ts.getListaClases().get(id.getLexema());
                mod = ((Metodo)metodo).getModMetodo();
                for(Llamada llamada : listaLlamadas){
                     t = llamada.check(clase, metodo,t,mod,ts);
                     mod = new ModificadorMetodo(new Token("dynamic", "dynamic", -1));
                 }                     
            }
            else{// es metodo dinamico A a = new A(); a.m1()...
                t = checkStatic(metodo);
                if(t == null)
                    throw new ErrorSemantico("la variable '" + id.getLexema() + "' no es accesible para metodo estatico. Linea: " + id.getLinea());
                mod = new ModificadorMetodo(new Token("dynamic", "dynamic", -1));
                for(Llamada llamada : listaLlamadas){
                     t = llamada.check(clase, metodo,t,mod,ts);
                } 
            }                
            if(t == null)
                throw new ErrorSemantico("la variable '" + id.getLexema() + "' no es accesible para metodo estatico. Linea: " + id.getLinea());

            return t;
    }
     
    private Tipo metodo_llamada_dinamico(Clase clase, Constructor metodo, Tipo t, ModificadorMetodo mod, TablaDeSimbolos ts) throws ErrorSemantico, IOException {
        Metodo actual = clase.getTablaMetodos().get(id.getLexema());   
        
        if(actual == null)
            throw new ErrorSemantico("El metodo '" + id.getLexema() + "' no se encuentra definido. Linea: " + id.getLinea());
        
        if(actual.getParametros().size() != args.size())
            throw new ErrorSemantico("La cantidad de argumentos para el metodo '" + id.getLexema() + "' no coinciden. Linea: " + id.getLinea());

        if(actual.getModMetodo().getModificador().equals("dynamic"))
            GeneradorDeCodigo.generar("LOAD 3 \n");

        t = actual.getTipoRetorno();
        if(!t.getNombre().equals("void")){
            GeneradorDeCodigo.generar("RMEM 1 \n");
            if(actual.getModMetodo().getModificador().equals("dynamic"))
                GeneradorDeCodigo.generar("SWAP \n");
        }                

        for(int i = 0 ; i < args.size() ; i ++){
            if(!args.get(i).check(clase, metodo,ts).conforma(actual.getParametros().get(i).getTipo()))
                throw new ErrorSemantico("Paramentros incompatibles en la linea "+ id.getLinea());
            if(actual.getModMetodo().getModificador().equals("dynamic"))
                GeneradorDeCodigo.generar("SWAP \n");
        }

        if(actual.getModMetodo().getModificador().equals("dynamic")){
            GeneradorDeCodigo.generar("DUP \n");
            GeneradorDeCodigo.generar("LOADREF 0 \n");
            GeneradorDeCodigo.generar("LOADREF "+ (actual.getOffset() - 1) + "\n");
            GeneradorDeCodigo.generar("CALL \n");
        }
        else{
            GeneradorDeCodigo.generar("PUSH "+ actual.definido().getNombre() + "_" + actual.getNombre() + "\n");
            GeneradorDeCodigo.generar("CALL \n");
        }

        mod = new ModificadorMetodo(new Token("dynamic", "dynamic", -1));
        for(Llamada llamada : listaLlamadas){
             t = llamada.check(clase, metodo,t,mod,ts);
        }

        return t;
    }

    private Tipo metodo_llamada_estatico(Clase clase, Constructor metodo, Tipo t, ModificadorMetodo mod, TablaDeSimbolos ts) throws ErrorSemantico, IOException {
        Metodo actual = clase.getTablaMetodos().get(id.getLexema());
        if (actual==null){
            throw new ErrorSemantico("metodo no definido en la linea " + id.getLinea());
        }
        if(!actual.getModMetodo().getModificador().equals("static"))
            throw new ErrorSemantico("Se esperaba un metodo estatico en la linea " + id.getLinea());
        if(actual.getParametros().size() != args.size())
            throw new ErrorSemantico("La cantidad de argumentos para el metodo '" + id.getLexema() + "' no coinciden. Linea: " + id.getLinea());

        t = actual.getTipoRetorno();                
        if(!t.getNombre().equals("void")){
            GeneradorDeCodigo.generar("RMEM 1 \n");
        }
        for(int i = 0 ; i < args.size() ; i ++){
            if(!args.get(i).check(clase, metodo,ts).conforma(actual.getParametros().get(i).getTipo()))
                throw new ErrorSemantico("Paramentros incompatibles en la linea "+ id.getLinea());
        }

        GeneradorDeCodigo.generar("PUSH " + clase.getNombre() + "_" + actual.getNombre() + "\n");
        GeneradorDeCodigo.generar("CALL \n");

        mod = new ModificadorMetodo(new Token("dynamic", "dynamic", -1));

        for(Llamada llamada : listaLlamadas){
             t = llamada.check(clase, metodo,t,mod,ts);
        }
            
        return t;
    }
}
